#include <stdlib.h>
#include <string.h>

#include "lib.h"
#include "stream.h"
using namespace Library;


Stream::Stream()
{
}

Stream::~Stream()
{
}

//  This function has been designed and written to be a _very_ generic string
//       reading function.  By being generic, this function can process any
//       style text file (DOS, Mac, and even Unix).  (This _should_ allow
//       predictable and reliable COMMON code to be written even on
//       drain-bramaged systems like 3DO.)
//       
char *
Stream::readString(char * buffer, int32 length)
{
	int32		location;
	int32		count;
	int32		loop;
	char *		ptr = buffer;

	location = tell();
	if (!buffer || length < 0 || location == Stream::eof)
		return 0;

	*buffer = '\0';

	if (length == 0)
		return buffer;

	//  We fill the entire buffer to avoid problems where '\r' and '\n'
	//     (under DOS) are seperated by the length - 1 cut off.  This allows
	//     us, should the seperation occur, to check the last character in
	//     'buffer' (even though it is invalid) to determine if we are 
	//     working with a DOS or a Mac file.  This way we can set the stream
	//     to the appropriate position for the next read.
	count = read(buffer, length);
	if (count == Stream::eof)
		return 0;

	// Make sure we terminate the data...(if we don't fill the buffer).
	if (count < length)
		ptr[count] = '\0';

	for (loop = 0; loop < count; loop++)
	{
		// Are we at the last (invalid) character of buffer?
		//     If so, then we have fullfilled one of the requirements...
		//     stopping at length - 1 characters.
		if (loop == (length - 1))
		{
			DEBUGMSG(("readString(): buffer size too small!"));
			ptr[loop] = '\0';
			break;
		}

		// We are allowing either DOS, Unix, or Mac string format...
		if ((ptr[loop] == '\n') || (ptr[loop] == '\r'))
		{
			if (ptr[loop] == '\r')			// DOS or Mac format.
			{ 
				ptr[loop++] = '\n';

				if (ptr[loop] == '\n')		// It's DOS.
					ptr[loop++] = '\0';		// Notice post-increment for DOS!
				else						// It's Mac.
					ptr[loop] = '\0';
			}
			else if (ptr[loop] == '\n')		// Unix format.
				ptr[++loop] = '\0';			// Notice pre-increment for Unix!

			break;
		}
	}

	seek(location + loop, Stream::beginning);

	return (char *)buffer;
}

char
Stream::readCharacter()
{
	char	buffer;

	if (read(&buffer, 1) == Stream::eof)
		return Stream::eof;

	return buffer;
}


//  This function has also been written to be _very_ generic.  It converts all
//       \n to \r\n and \r to \r\n while leaving any \r\n as they are.
//
char *
Stream::writeString(char * string)
{
	char *		buffer;
	char *		src;
	char *		dst;
	int32		length;
	char		prevch;

	if (!string)
		return 0;

	buffer	= new char[ strlen(string) << 1 ];	// Worst case...all '\n's.
	if (!buffer)
		return 0;

	src		= string;
	dst		= buffer;
	prevch	= '\0';
	length	= 0;

	while (*src)
	{
		// We are looking for the UNIX style \n, not the trailing half of
		//    a DOS style \r\n.
		if (*src == '\n' && prevch != '\r')
		{
			*dst++ = '\r';
			*dst++ = '\n';
			length += 2;
		}

		// We are looking for the Mac style \r, not the leading half of
		//    a DOS style \r\n.
		else if (*src != '\n' && prevch == '\r')
		{
			*dst++ = '\n';
			*dst++ = *src;
			length += 2;
		}
		else
		{
			*dst++ = *src;
			length += 1;
		}

		prevch = *src++;
	}

	// If the last character is a '\r' we need to add the trailing '\n'.
	if (prevch == '\r')
	{
		*dst++ = '\n';
		length += 1;
	}

	length = write(buffer, length) == length ? length : 0;

	delete [] buffer;

	return length ? buffer : 0;
}

char
Stream::writeCharacter(char c)
{
	if (write(&c, 1) != 1)
		return Stream::eof;

	return c;
}

//  This function has been designed and written to be a _very_ generic string
//       reading function.  By being generic, this function can process any
//       style text file (DOS, Mac, and even Unix).  (This _should_ allow
//       predictable and reliable COMMON code to be written even on
//       drain-bramaged systems like 3DO.)
//       
uint16*
Stream::readString(uint16* buffer, int32 length)
{
	int32		location;
	int32		count;
	int32		loop;
	uint16*		ptr = buffer;

	location = tell();
	if (!buffer || length < 0 || location == Stream::eof)
		return 0;

	*buffer = '\0';

	if (length == 0)
		return buffer;

	//  We fill the entire buffer to avoid problems where '\r' and '\n'
	//     (under DOS) are seperated by the length - 1 cut off.  This allows
	//     us, should the seperation occur, to check the last character in
	//     'buffer' (even though it is invalid) to determine if we are 
	//     working with a DOS or a Mac file.  This way we can set the stream
	//     to the appropriate position for the next read.
	count = read(buffer, length*sizeof(uint16));
	if (count == Stream::eof)
		return 0;

	count /= sizeof(uint16);

	// Make sure we terminate the data...(if we don't fill the buffer).
	if (count < length)
		ptr[count] = '\0';

	for (loop = 0; loop < count; loop++)
	{
		// Are we at the last (invalid) character of buffer?
		//     If so, then we have fullfilled one of the requirements...
		//     stopping at length - 1 characters.
		if (loop == (length - 1))
		{
			DEBUGMSG(("readString(): buffer size too small!"));
			ptr[loop] = '\0';
			break;
		}

		// We are allowing either DOS, Unix, or Mac string format...
		if ((ptr[loop] == '\n') || (ptr[loop] == '\r'))
		{
			if (ptr[loop] == '\r')			// DOS or Mac format.
			{ 
				ptr[loop++] = '\n';

				if (ptr[loop] == '\n')		// It's DOS.
					ptr[loop++] = '\0';		// Notice post-increment for DOS!
				else						// It's Mac.
					ptr[loop] = '\0';
			}
			else if (ptr[loop] == '\n')		// Unix format.
				ptr[++loop] = '\0';			// Notice pre-increment for Unix!

			break;
		}
	}

	seek(location + loop*sizeof(uint16), Stream::beginning);

	return (uint16*)buffer;
}

uint16
Stream::readCharacter16()
{
	uint16	buffer;

	if (read(&buffer, sizeof(uint16)) == Stream::eof)
		return (uint16)Stream::eof;

	return buffer;
}


//  This function has also been written to be _very_ generic.  It converts all
//       \n to \r\n and \r to \r\n while leaving any \r\n as they are.
//
uint16*
Stream::writeString(uint16* string)
{
	uint16*		buffer;
	uint16*		src;
	uint16*		dst;
	int32		length;
	uint16		prevch;

	if (!string)
		return 0;

	buffer	= new uint16[ wcslen(string) << 1 ];	// Worst case...all '\n's.
	if (!buffer)
		return 0;

	src		= string;
	dst		= buffer;
	prevch	= '\0';
	length	= 0;

	while (*src)
	{
		// We are looking for the UNIX style \n, not the trailing half of
		//    a DOS style \r\n.
		if (*src == '\n' && prevch != '\r')
		{
			*dst++ = '\r';
			*dst++ = '\n';
			length += 2;
		}

		// We are looking for the Mac style \r, not the leading half of
		//    a DOS style \r\n.
		else if (*src != '\n' && prevch == '\r')
		{
			*dst++ = '\n';
			*dst++ = *src;
			length += 2;
		}
		else
		{
			*dst++ = *src;
			length += 1;
		}

		prevch = *src++;
	}

	// If the last character is a '\r' we need to add the trailing '\n'.
	if (prevch == '\r')
	{
		*dst++ = '\n';
		length += 1;
	}

	length = write(buffer, length) == length ? length : 0;

	delete [] buffer;

	return length ? buffer : 0;
}

uint16
Stream::writeCharacter(uint16 c)
{
	if (write(&c, sizeof(uint16)) != 1)
		return (uint16)Stream::eof;

	return c;
}



//----------------------------------------------------------------------------
// Old Depricated Functions
//----------------------------------------------------------------------------


//  This function has been designed and written to be a _very_ generic string
//       reading function.  By being generic, this function can process any
//       style text file (DOS, Mac, and even Unix).  (This _should_ allow
//       predictable and reliable COMMON code to be written even on
//       drain-bramaged systems like 3DO.)
//       
char *
CStreamGetString(pCStream This, void *Buffer, uint32 Length)
{
	uint32		location;
	uint32		count;
	uint32		loop;
	char *		ptr = (char *)Buffer;


	if ((location = CStreamTell(This)) == streamEOF)
		return NULL;

	//  We fill the entire buffer to avoid problems where '\r' and '\n'
	//     (under DOS) are seperated by the Length - 1 cut off.  This allows
	//     us, should the seperation occur, to check the last character in
	//     'Buffer' (even though it is invalid) to determine if we are 
	//     working with a DOS or a Mac file.  This way we can set the stream
	//     to the appropriate position for the next read.
	if ((count = CStreamRead(This, Buffer, Length)) == streamEOF)
		return NULL;

	// Make sure we terminate the data...(if we don't fill the buffer).
	if (count < Length)
		ptr[count] = '\0';

	for (loop = 0; loop < count; loop++)
	{
		// Are we at the last (invalid) character of Buffer?
		//     If so, then we have fullfilled one of the requirements...
		//     stopping at Length - 1 characters.
		if (loop == (Length - 1))
		{
			ptr[loop] = '\0';
			DEBUGMSG(("CStreamGetString(): Buffer size too small!"));
			DEBUGMSG(("                    Expect your program to _CRASH_!"));
			break;
		}

		// We are allowing either DOS, Unix, or Mac string format...
		if ((ptr[loop] == '\n') || (ptr[loop] == '\r'))
		{
			// DOS or Mac type format.
			if (ptr[loop] == '\r')
			{ 
				ptr[loop++] = '\n';

				// DOS unique type format.
				if (ptr[loop] == '\n')
				{
					ptr[loop] = '\0';
					loop++;					// Notice the extra loop++ for DOS!
				}
				else
					ptr[loop] = '\0';
			}
			// Unix type format.
			else if (ptr[loop] == '\n')
			{
				loop++;
				ptr[loop] = '\0';
			}

			break;
		}
	}

	CStreamSeek(This, location + loop, streamSEEK_SET);

	return (char *)Buffer;
}


char
CStreamGetChar(pCStream This)
{
	char	buffer;

	if (CStreamRead(This, &buffer, 1) == streamEOF)
		return streamEOF;

	return buffer;
}




//  This function has also been written to be _very_ generic.  It converts all
//       \n to \r\n and \r to \r\n while leaving any \r\n as they are.
//
#ifndef CSTREAM_READONLY_FILESYSTEM

char *
CStreamPutString(pCStream This, void *Buffer)
{
	char *		buffer;
	char *		srcptr;
	char *		dstptr;
	uint32		length;
	char		prevch;


	buffer = (char *)malloc(strlen((char *)Buffer) << 1);	// Worst case...all '\n's.

	if (buffer)
	{
		srcptr = (char *)Buffer;
		dstptr = buffer;
		prevch = '\0';
		length = 0;

		while (*srcptr)
		{
			// We are looking for the UNIX style \n, not the trailing half of
			//    a DOS style \r\n.
			if (*srcptr == '\n' && prevch != '\r')
			{
				*dstptr++ = '\r';
				*dstptr++ = '\n';
				length += 2;
			}

			// We are looking for the Mac style \r, not the leading half of
			//    a DOS style \r\n.
			else if (*srcptr != '\n' && prevch == '\r')
			{
				*dstptr++ = '\n';
				*dstptr++ = *srcptr;
				length += 2;
			}
			else
			{
				*dstptr++ = *srcptr;
				length += 1;
			}

			prevch = *srcptr++;
		}

		// If the last character is a '\r' we need to add the trailing '\n'.
		if (prevch == '\r')
		{
			*dstptr++ = '\n';
			length += 1;
		}

		if (CStreamWrite(This, buffer, length) == (int32)length)
		{
			free(buffer);
			return((char *)Buffer);
		}

		free(buffer);
	}

	return NULL;
}


char
CStreamPutChar(pCStream This, char Char)
{
	if (CStreamWrite(This, &Char, 1) != 1)
		return streamEOF;

	return Char;
}

#endif
